## ADVENT OF CODE 2019 DAY X
#
# part 1:
#
# part 2: stalled because i can't figure out when the feedback loop is actually
# supposed to halt, what a miserable place to be.

defmodule Intcode do
  def calc(program, phase, signal) do
    calc_unit(program, 0, 0, phase, signal, 0)
  end

  def make_digits(program, pointer) do
    Enum.at(program, pointer)
    |> Integer.to_string()
    |> String.codepoints()
    |> Enum.map(&String.to_integer/1)
    |> Enum.reverse()
  end

  def get_opcode(digits) do
    Enum.join([Enum.at(digits, 1), Enum.at(digits, 0)])
    |> String.to_integer()
  end

  def calc_unit(program, pointer, step, phase, signal, output) do
    digits = make_digits(program, pointer)
    opcode = get_opcode(digits)
    #IO.puts(opcode)

    case opcode do
      99 -> halt(output)
      1 -> op1(digits, program, pointer, step, phase, signal, output)
      2 -> op2(digits, program, pointer, step, phase, signal, output)
      3 -> op3(program, pointer, step, phase, signal, output)
      4 -> op4(program, pointer, step, phase, signal, output)
      5 -> op5(digits, program, pointer, step, phase, signal, output)
      6 -> op6(digits, program, pointer, step, phase, signal, output)
      7 -> op7(digits, program, pointer, step, phase, signal, output)
      8 -> op8(digits, program, pointer, step, phase, signal, output)
    end
  end

  def halt(output) do
    #IO.puts("HALT")
    output
  end

  def two_ops(digits, program, pointer) do
    m1 = Enum.at(digits, 2, 0)
    m2 = Enum.at(digits, 3, 0)

    p1 = Enum.at(program, pointer+1)
    p2 = Enum.at(program, pointer+2)
    p3 = Enum.at(program, pointer+3)

    op1 =
      if m1 == 0 do
        Enum.at(program, p1)
      else
        p1
      end

    op2 =
      if m2 == 0 do
        Enum.at(program, p2)
      else
        p2
      end

    {op1, op2, p3}
  end

  def op1(digits, program, pointer, step, phase, signal, output) do
    # (a+b)

    {op1, op2, p3} = two_ops(digits, program, pointer)
    value = op1 + op2
    calc_unit(List.insert_at(List.delete_at(program, p3), p3, value), pointer+4, step, phase, signal, output)
  end

  def op2(digits, program, pointer, step, phase, signal, output) do
    # (a*b)

    {op1, op2, p3} = two_ops(digits, program, pointer)
    value = op1 * op2
    calc_unit(List.insert_at(List.delete_at(program, p3), p3, value), pointer+4, step, phase, signal, output)
  end

  def op3(program, pointer, step, phase, signal, output) do
    # (input)

    store = Enum.at(program, pointer+1)
    input =
    if step == 0 do
      phase
    else
      signal
    end

    calc_unit(List.insert_at(List.delete_at(program, store), store, input), pointer+2, step+1, phase, signal, output)
  end

  def op4(program, pointer, step, phase, signal, output) do
    # (output)

    loc = Enum.at(program, pointer+1)
    output = Enum.at(program, loc)
    #IO.puts(output)
    calc_unit(program, pointer+2, step, phase, signal, output)
    #output
  end

  def op5(digits, program, pointer, step, phase, signal, output) do

    {op1, op2, p3} = two_ops(digits, program, pointer)

    value =
      if op1 != 0 do
        op2
      else
        pointer+3
      end

    calc_unit(program, value, step, phase, signal, output)
  end

  def op6(digits, program, pointer, step, phase, signal, output) do

    {op1, op2, p3} = two_ops(digits, program, pointer)

    value = 
      if op1 == 0 do
        op2
      else
        pointer+3
      end

    calc_unit(program, value, step, phase, signal, output)
  end

  def op7(digits, program, pointer, step, phase, signal, output) do
    {op1, op2, p3} = two_ops(digits, program, pointer)

    value =
      if op1 < op2 do
        1
      else
        0
      end

    calc_unit(List.insert_at(List.delete_at(program, p3), p3, value), pointer+4, step, phase, signal, output)

  end

  def op8(digits, program, pointer, step, phase, signal, output) do
    {op1, op2, p3} = two_ops(digits, program, pointer)

    value = 
      if op1 == op2 do
        1
      else
        0
      end

    calc_unit(List.insert_at(List.delete_at(program, p3), p3, value), pointer+4, step, phase, signal, output)
  end
end

## part 1

defmodule PartOne do
  def process_input(inputfile) do
    File.read!(inputfile)
    |> String.trim()
    |> String.split(",")
    |> Enum.map(&String.to_integer/1)
  end

  def calc(program) do
    sequences = Perms.perms([0,1,2,3,4])
    Enum.reduce(sequences, [], fn sequence, outputs ->
      [calc_sequence(program, sequence)|outputs]
    end)
    |> Enum.sort()
    |> Enum.reverse()
    |> hd()
  end

  def calc_sequence(program, sequence) do
    Enum.reduce(sequence, 0, fn phase, signal ->
      calc_pass(program, phase, signal)
    end)
  end

  def calc_pass(program, phase, signal) do
    Intcode.calc(program, phase, signal)
  end
end

defmodule Perms do
## stealing some stack overflow cod
  def perms(%MapSet{} = set),
    do: MapSet.to_list(set) |> perms

  def perms([]), do: [[]]

  def perms(l) do
      for h <- l, t <- perms(l -- [h]),
        do: [h|t]
    end
end

IO.puts("PART ONE")

# test input

#IO.write("1.1: 43210 == ")
#PartOne.calc_sequence([3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0], [4,3,2,1,0])
#|> IO.puts()

#IO.write("1.2: 54321 == ")
#PartOne.calc_sequence([3,23,3,24,1002,24,10,24,1002,23,-1,23, 101,5,23,23,1,24,23,23,4,23,99,0,0], [0,1,2,3,4])
#|> IO.puts()

#IO.write("1.3: 65210 == ")
#PartOne.calc_sequence([3,31,3,32,1002,32,10,32,1001,31,-2,31,1007,31,0,33, 1002,33,7,33,1,33,31,31,1,32,31,31,4,31,99,0,0,0], [1,0,4,3,2])
#|> IO.puts()

# puzzle input

#IO.write("PUZZLE: ")
#PartOne.process_input("2019-07-input.txt")
#|> PartOne.calc()
#|> IO.puts()

## part 2

defmodule PartTwo do
  def calc_sequence(program, sequence, initial, pass) when pass < 15 do
    #IO.gets("type something: ")
    step = 
    Enum.reduce(sequence, initial, fn phase, signal ->
      calc_pass(program, phase, signal)
    end)

    IO.puts(step)

    calc_sequence(program, sequence, step, pass+1)
  end

  def calc_sequence(program, sequence, initial, pass) when pass >= 15 DO
    initial 
  end

  def calc_pass(program, phase, signal) do
    ans = Intcode.calc(program, phase, signal)
    #IO.puts(ans)
    ans
  end
end

#IO.puts("PART TWO")

# test input

IO.write("2.1: 139629729 == ")
PartTwo.calc_sequence([3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26, 27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5], [9,8,7,6,5], 0, 0)
|> IO.puts()

IO.write("2.2: 18216 == ")
PartTwo.calc_sequence([3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54, -5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4, 53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10], [9,7,8,5,6], 0, 0)
|> IO.puts()

# puzzle input
#IO.write("PUZZLE: ")
